/*
 * Copyright (c) 2012-2013 Open Source Community - <http://www.peerfact.org>
 * Copyright (c) 2011-2012 University of Paderborn - UPB
 * Copyright (c) 2005-2011 KOM - Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 * 
 * PeerfactSim.KOM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * PeerfactSim.KOM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.peerfact.impl.overlay.dht.pastry.parallel.components;

import java.math.BigInteger;

import org.peerfact.impl.overlay.dht.pastry.base.components.PastryContact;
import org.peerfact.impl.overlay.dht.pastry.base.nodestate.RouteSet;
import org.peerfact.impl.simengine.Simulator;

/**
 * This class represents the routing table of node in the pastry overlay.
 * 
 * @author Julius Rueckert <peerfact@kom.tu-darmstadt.de>
 * 
 * @version 05/06/2011
 */
public class PastryRoutingTable extends
		org.peerfact.impl.overlay.dht.pastry.base.components.PastryRoutingTable {

	/**
	 * rowBucketsize should be true if the Bucket size should depend on the row
	 * of the Routing table entries. exponentialShrink should be true if the
	 * Buckets should shrink exponentially depending on the row of the entries.
	 * Up to a Minimum of maxRedundance; by Martin Wiegand
	 */
	private boolean rowBucketsize = true;

	/**
	 * should be true if the amount of routing table entries should shrink
	 * exponentially; by Martin Wiegand
	 */
	private boolean exponentialShrink = true;

	/**
	 * amount of redundant routing entries; by Martin Wiegand
	 */
	private int maxRedundance = 2;

	/**
	 * set when buckets are replaced using diversity first
	 */
	private boolean diversityFirst = false;

	public PastryRoutingTable(PastryNode node) {
		super(node);
	}

	// new constructor due to the new configureable variables; by Martin Wiegand
	public PastryRoutingTable(PastryNode node, int maxRedundance,
			boolean rowBucketsize, boolean exponentialShrink,
			boolean diversityFirst) {
		super(node);
		this.maxRedundance = maxRedundance;
		this.rowBucketsize = rowBucketsize;
		this.exponentialShrink = exponentialShrink;
		this.diversityFirst = diversityFirst;
	}

	/**
	 * Insert a contact to the set
	 * 
	 * @param c
	 */
	@Override
	public void insertContact(PastryContact c) {
		if (c.getOverlayID().equals(nodeId)) {
			return;
		}

		RouteSet setToInsertIn = makeBestEntry(c.getOverlayID());
		if (!setToInsertIn.contains(c)) {
			setToInsertIn.insertEntry(c);
			cleanSet(setToInsertIn);
			if (setToInsertIn.contains(c)) {
				lastChanged = Simulator.getCurrentTime();
			}
		}
	}

	/**
	 * Makes sure that the given set's size does not exceed the defined maximum
	 * size.
	 * 
	 * @param s
	 *            the set to check
	 */
	private void cleanSet(RouteSet s) {
		int currentSize = maxRedundance;
		// changes to determine current RouteSets size; by Martin Wiegand
		if (rowBucketsize) {
			int diffDigit;
			if (exponentialShrink) {
				// set the current size to the amount of rows divided by 2 to
				// the power of the amount of rows minus the index of
				// the most significant different bit or maxRedundance,
				// whichever is hiher
				for (PastryContact c : s) {
					diffDigit = nodeId.indexOfMSDD(c.getOverlayID());
					currentSize = (int) java.lang.Math.max(NUM_OF_ROWS
							/ java.lang.Math
									.pow(2.0, (NUM_OF_ROWS - diffDigit)),
							maxRedundance);
					break;
				}
			} else {
				// set the current size to the amount of rows minus the index of
				// the most significant different bit.
				for (PastryContact c : s) {
					diffDigit = nodeId.indexOfMSDD(c.getOverlayID());
					currentSize = NUM_OF_ROWS - diffDigit;
					break;
				}
			}
		}
		if (currentSize <= 0) {
			currentSize = maxRedundance;
		}
		// end of changes
		while (s.getSize() > currentSize) {
			/*
			 * FIXME: Here we have to decide what to do. In my opinion we should
			 * keep the entries with the best proximity metric value compared to
			 * the owning peers.
			 */
			// implementation of diversity first; by Martin Wiegand
			if (diversityFirst) {
				BigInteger lowestDistanceSum, lowestDistanceA, lowestDistanceB, temp;
				PastryContact toRemove;
				toRemove = null;
				// initialize distances.
				lowestDistanceA = BigInteger.ZERO;
				lowestDistanceB = BigInteger.ZERO;
				lowestDistanceSum = BigInteger.ZERO;
				lowestDistanceSum.setBit(129);
				for (PastryContact c : s) {
					lowestDistanceA.setBit(129);
					lowestDistanceB.setBit(129);
					for (PastryContact d : s) {
						// compare the distances of all nodes contained within a
						// RouteSet to all other nodes within the RouteSet
						if (c.equals(d)) {
							continue;
						}
						temp = c.getOverlayID().getUniqueValue().xor(
								d.getOverlayID().getUniqueValue());
						if (temp.compareTo(lowestDistanceA) == -1) {
							lowestDistanceB = lowestDistanceA;
							lowestDistanceA = temp;
						} else if (temp.compareTo(lowestDistanceB) == -1) {
							lowestDistanceB = temp;
						}
					}
					// remove the node closest to its direct neighbors
					temp = lowestDistanceA.add(lowestDistanceB);
					if (temp.compareTo(lowestDistanceSum) == -1) {
						lowestDistanceSum = temp;
						toRemove = c;
					}
				}
				if (toRemove != null) {
					s.removeEntry(toRemove);
				} else {
					// if no entry is found remove a random entry
					PastryContact contactToRemove = s.iterator().next();
					s.removeEntry(contactToRemove);

					lastChanged = Simulator.getCurrentTime();
				}
				// changes by Martin Wiegand end.
			} else {
				// For now: Remove any of the contacts
				PastryContact contactToRemove = s.iterator().next();
				s.removeEntry(contactToRemove);

				lastChanged = Simulator.getCurrentTime();
			}
		}

	}

	public static int getNumOfRows() {
		return NUM_OF_ROWS;
	}

	public static int getNumOfColumns() {
		return NUM_OF_COLUMNS;
	}

	public RouteSet getEntry(int diffDigit, int digit) {
		return routingTable[diffDigit][digit];
	}

	// implemented by Martin Wiegand.
	// computes the average amount of contacts stored within a single entry in
	// the row diffDigit
	public double getBucketStateLevel(int diffDigit) {
		double average = 0.0;
		double yay = 0.0;
		RouteSet rs = null;
		for (int i = 0; i < NUM_OF_COLUMNS; i++) {
			if (i != nodeId.getDigit(diffDigit)) {
				rs = getEntry(diffDigit, i);
				if (rs != null) {
					int temp = rs.getSize();
					yay = new Double(temp);
				} else {
					yay = 0.0;
				}
				average += yay / (NUM_OF_COLUMNS - 1);
			}
		}
		return new Double(average);
	}

}
